﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Emgu.CV;
using System.Drawing;
using Emgu.CV.Structure;

namespace svc
{
    public class SpaceDetectorSettings
    {
        public SpaceDetectorSettings()
        {
            GrayColor = 150;
            Debug = true;
            Scaling = 1.0F;
        }

        public float Scaling { get; set; }
        public int GrayColor { get; set; }
        public bool Debug { get; set; }
    }
    public class SpaceDetector
    {
        private SpaceDetectorSettings _settings;
        public SpaceDetector(SpaceDetectorSettings settings)
        {
            _settings = settings;
        }

        public void DetectSpaces(ParkingSpace bbox, ParkingSpace[] zones, ImageFrame frame)
        {
            using (var source = PerspectiveTransform(frame, Scale(bbox, _settings.Scaling)))
            using (var gray = RGB2Gray(source, Scale(bbox, _settings.Scaling)))
            {
                SubstractBckground(gray, _settings.GrayColor);
                using (var bw = OtsuBinarize(gray, Scale(bbox, _settings.Scaling)))
                {
                    TestSpaces(bw, source, zones);

                    if (_settings.Debug)
                    {
                        CvInvoke.cvShowImage("frame", frame.Ptr);
                        CvInvoke.cvShowImage("binary", bw.Ptr);
                        CvInvoke.cvShowImage("source", source.Ptr);
                    }
                }
            }

        }

        private ParkingSpace Scale(ParkingSpace src, float fraction)
        {
            return new ParkingSpace((int)(src.X * fraction),
                                    (int)(src.Y * fraction),
                                    (int)((src.X + src.width) * fraction),
                                    (int)((src.Y + src.height) * fraction))
            {
                ID = src.ID
            };
        }
        private void TestSpaces(ImageFrame bw, ImageFrame src, ParkingSpace[] zones)
        {
            foreach (var unscaledZone in zones)
            {
                var zone = Scale(unscaledZone, _settings.Scaling);
                using (ImageFrame imzone = new ImageFrame(CvInvoke.cvCreateImage(new Size(zone.width, zone.height), Emgu.CV.CvEnum.IPL_DEPTH.IPL_DEPTH_8U, 1)))
                {
                    CvInvoke.cvGetRectSubPix(bw.Ptr, imzone.Ptr, new PointF(zone.X + zone.width / 2, zone.Y + zone.height / 2));

                    if (_settings.Debug)
                    {
                        MCvScalar color;
                        if (CvInvoke.cvCountNonZero(imzone.Ptr) > zone.width * zone.height / 4)
                            color = new MCvScalar(0, 0, 255);
                        else
                            color = new MCvScalar(0, 255, 0);

                        MCvFont font = new MCvFont(Emgu.CV.CvEnum.FONT.CV_FONT_HERSHEY_SIMPLEX, 1, 1);
                        CvInvoke.cvPutText(src.Ptr, zone.ID.ToString(), new Point(zone.X, zone.Y + 20), ref font, color);
                        CvInvoke.cvRectangle(src.Ptr, new Point(zone.X, zone.Y), new Point(zone.X + zone.width, zone.Y + zone.height), color, 2, Emgu.CV.CvEnum.LINE_TYPE.EIGHT_CONNECTED, 0);
                        CvInvoke.cvRectangle(bw.Ptr, new Point(zone.X, zone.Y), new Point(zone.X + zone.width, zone.Y + zone.height), new MCvScalar(255), 1, Emgu.CV.CvEnum.LINE_TYPE.EIGHT_CONNECTED, 0);
                    }
                }
            }
        }

        public ImageFrame RGB2Gray(ImageFrame imsrc, ParkingSpace bbox)
        {
            ImageFrame imdest = new ImageFrame(CvInvoke.cvCreateImage(new Size(bbox.width, bbox.height), Emgu.CV.CvEnum.IPL_DEPTH.IPL_DEPTH_8U, 1));
            CvInvoke.cvCvtColor(imsrc.Ptr, imdest.Ptr, Emgu.CV.CvEnum.COLOR_CONVERSION.CV_RGB2GRAY);
            return imdest;
        }
        public ImageFrame OtsuBinarize(ImageFrame imsrc, ParkingSpace bbox)
        {
            ImageFrame imdest = new ImageFrame(CvInvoke.cvCreateImage(new Size(bbox.width, bbox.height), Emgu.CV.CvEnum.IPL_DEPTH.IPL_DEPTH_8U, 1));
            CvInvoke.cvThreshold(imsrc.Ptr, imdest.Ptr, 0, 255, Emgu.CV.CvEnum.THRESH.CV_THRESH_BINARY | Emgu.CV.CvEnum.THRESH.CV_THRESH_OTSU);
            return imdest;
        }

        public void SubstractBckground(ImageFrame imsrc, int value)
        {
            CvInvoke.cvAbsDiffS(imsrc.Ptr, imsrc.Ptr, new MCvScalar(value));
        }

        public ImageFrame PerspectiveTransform(ImageFrame imsrc, ParkingSpace bbox)
        {
            PointF[] source = new PointF[]
                {
                    new PointF(169, 29),
                    new PointF(489, 62),
                    new PointF(-145, 350),
                    new PointF(356, 469)
                };
            PointF[] dest = new PointF[]
            {
                    new PointF(0, 0),
                    new PointF(bbox.width, 0),
                    new PointF(0, bbox.height),
                    new PointF(bbox.width, bbox.height)
            };

            ImageFrame imdest = ImageFrame.Create(bbox.width, bbox.height, Emgu.CV.CvEnum.IPL_DEPTH.IPL_DEPTH_8U, 3);
            using (var mat = new Matrix(CvInvoke.cvCreateMat(3, 3, Emgu.CV.CvEnum.MAT_DEPTH.CV_32F)))
            {
                CvInvoke.cvGetPerspectiveTransform(source, dest, mat.Ptr);
                CvInvoke.cvWarpPerspective(imsrc.Ptr, imdest.Ptr, mat.Ptr, 0, new MCvScalar(255));
            }
            return imdest;
        }

    }
}
